using System;
using System.Linq;
using System.Text;
using NUnit.Framework;
using System.Collections.Generic;
using ProjectEuler.Core.Helpers;

namespace ProjectEuler.Core
{
    public class Problem51 : IProjectEulerProblem
    {
        protected int SmallestPrimeWithFamilySizeOf(int familysize)
        {
            foreach (var prime in MathHelper.GetPrimes())
            {
                if(FamilySize(prime) == familysize)
                {
                    return prime;
                }
            }
            return -1;
        }

        protected static int FamilySize(long prime)
        {
            if (prime < 10) return 0;

            var numdigits = prime.ToString().Length;
            var familysize = 0;
            foreach(var mask in GetAllMasks(numdigits))
            {
                var family = GetFamily(prime, mask);

                // the given prime itself must be in the generated family!
                if(family.Contains(prime) && (family.Count() > familysize))
                {
                    familysize = family.Count();
                }
            }
            return familysize;
        }

        private static IEnumerable<long> GetFamily(long prime, string mask)
        {
            for(int i=0;i<10;i++)
            {
                var generatedNum = ApplyMask(prime, mask, i);
                if (MathHelper.IsPrime(generatedNum))
                {
                    yield return generatedNum;
                }
            }
        }

        private static IEnumerable<string> GetAllMasks(int numdigits)
        {
            int mask = 1;
            var maskString = GetMask(mask, numdigits);
            var maxmask = new string('1', numdigits);
            while (maskString != maxmask)
            {
                yield return maskString;
                mask++;
                maskString = GetMask(mask, numdigits);
            }
            yield break;
        }

        private static string GetMask(int i, int len)
        {
            var str = Convert.ToString(i, 2);
            while (str.Length < len)
            {
                str = "0" + str;
            }
            return str;
        }

        private static long ApplyMask(long prime, string mask, long num)
        {
            var primestr = new StringBuilder(prime.ToString());
            var replacechar = num.ToString()[0];
            for (int j = 0; j < mask.Length; j++)
            {
                if(mask[j] == '1')
                {
                    primestr[j] = replacechar;
                }
            }
            var applied = long.Parse(primestr.ToString());
            // if it's not the same length (i.e. there was a leading 0) it doesn't count
            // so just return a non-prime
            if(applied.ToString().Length != mask.Length)
            {
                return 1;
            }
            return applied;
        }

        public int Number
        {
            get { return 51; }
        }

        public string Description
        {
            get { return "Find the smallest prime which, by replacing part of the number with the same digit, is part of an eight prime value family."; }
        }

        public string Answer
        {
            get { return SmallestPrimeWithFamilySizeOf(8).ToString(); }
        }
    }

    [TestFixture]
    public class Problem51Tests : Problem51
    {
        [Test]
        public void Then_13_should_be_smallest_prime_with_family_of_6()
        {
            Assert.AreEqual(13, SmallestPrimeWithFamilySizeOf(6));
        }

        [Test]
        public void Then_56003_should_be_smallest_prime_with_family_of_7()
        {
            Assert.AreEqual(56003, SmallestPrimeWithFamilySizeOf(7));
        }
    }
}